#ifndef ZMQ_TUTORIAL_INC_HELPER_HPP
#define ZMQ_TUTORIAL_INC_HELPER_HPP

#include <iostream>
#include <random>
#include <string>
#include <vector>
#include <deque>

#include <absl/time/clock.h>
#include <spdlog/spdlog.h>
#include <zmq.hpp>

using absl::Now;
using absl::Time;
using absl::Duration;
using absl::Seconds;

using spdlog::info;

inline void send_string(zmq::socket_t& s, std::string msg)
{
	s.send(msg.data(), msg.size());
}

inline std::string receive_string(zmq::socket_t& s)
{
	zmq::message_t msg;
	s.recv(&msg);
	return std::string((char*)msg.data(), msg.size());
}

inline zmq::message_t to_message(std::string msg_str)
{
	return zmq::message_t(msg_str.data(), msg_str.size());
}

inline std::string to_string(zmq::message_t& msg)
{
	return std::string(msg.data<char>(), msg.size());
}

inline void pause()
{
	int tmp;
	std::cin >> tmp;
}

std::string dump_msg(zmq::message_t& msg)
{
	std::stringstream ss;

	char* data = msg.data<char>();
	bool is_text = true;
	for (size_t i = 0; i < msg.size(); i++) {
		if (data[i] < 32 || data[i] > 127)
			is_text = false;
	}

	ss << "[" << std::setfill('0') << std::setw(3) << msg.size() << "] ";
	for (size_t i = 0; i < msg.size(); i++) {
		if (is_text)
			ss << (char)data[i];
		else
			ss << std::setfill('0') << std::setw(2)
			<< std::hex << (unsigned int)(unsigned char)data[i];
	}

	return ss.str();
}

void dump_msgs(std::deque<zmq::message_t>& msgs)
{
	info("-------------------------------------");
	for (zmq::message_t& msg : msgs)
	{
		info(dump_msg(msg));
	}
	info("-------------------------------------");
}

void dump_recved_msg(zmq::socket_t& s)
{
	while (true)
	{
		zmq::message_t msg;
		s.recv(&msg);
		info(dump_msg(msg));
		if (!msg.more())
		{
			break;
		}
	}
}

inline std::deque<zmq::message_t> zmsg_recv(zmq::socket_t& s)
{
	std::deque<zmq::message_t> msgs;
	while (true)
	{
		zmq::message_t msg;
		s.recv(&msg);
		msgs.push_back(std::move(msg));

		if (!msgs.back().more())
		{
			break;
		}
	}
	return msgs;
}

inline std::deque<zmq::message_t> copy(std::deque<zmq::message_t>& msgs)
{
	std::deque<zmq::message_t> copied(msgs.size());
	for (size_t i = 0; i < msgs.size(); ++i)
	{
		copied[i].copy(&msgs[i]);
	}
	return copied;
}

template<typename T>
void zmsg_send_more(zmq::socket_t s, T beg, T end)
{
	if (beg == end)
	{
		return;
	}

	for (T it = beg; it != end; ++it)
	{
		s.send(*it, ZMQ_SNDMORE);
	}
}

template<typename T>
void zmsg_send(zmq::socket_t& s, T beg, T end)
{
	if (beg == end)
	{
		return;
	}
	
	T it = beg;
	for (T next = it + 1; next != end; ++it, ++next)
	{
		s.send(*it, ZMQ_SNDMORE);
	}
	s.send(*it);
}

inline void zmsg_send(zmq::socket_t& s, std::deque<zmq::message_t>& msgs)
{
	zmsg_send(s, msgs.begin(), msgs.end());
}

inline std::string gen_random_id()
{
	std::random_device rd;
	std::uniform_int_distribution<int> dist(0, 10000);
	std::string id = fmt::format("{:04X}-{:04X}", dist(rd), dist(rd));
	return id;
}

inline void set_random_id(zmq::socket_t& s)
{
	std::random_device rd;
	std::uniform_int_distribution<int> dist(0, 10000);
	std::string id = fmt::format("{:04X}-{:04X}", dist(rd), dist(rd));
	s.setsockopt(ZMQ_IDENTITY, id.data(), id.size());
}

#endif // ZMQ_TUTORIAL_INC_HELPER_HPP
